use std::any::Any;
use std::collections::VecDeque;
use std::sync::Arc;

use winit::window::Window;

use graphics::Text;

use crate::id::IdPath;
use crate::message::Message;
use crate::widget::PodFlags;
use crate::widget::WidgetState;

pub struct CxState {
    pub window: Arc<Window>,
    pub messages: VecDeque<Message>,
}

pub struct EventCtx<'a> {
    pub cx_state: &'a mut CxState,
    pub is_handled: bool,
    pub widget_state: &'a mut WidgetState,
}

pub struct PaintCtx<'a> {
    pub cx_state: &'a mut CxState,
    pub widget_state: &'a mut WidgetState,
    pub brush_cx: Text,
}

pub struct LayoutCtx<'a> {
    pub cx_state: &'a mut CxState,
    pub widget_state: &'a mut WidgetState,
    pub brush_cx: Text,
}

pub struct LifeCycleCtx<'a> {
    pub cx_state: &'a mut CxState,
    pub widget_state: &'a mut WidgetState,
}

impl EventCtx<'_> {
    pub fn set_active(&mut self, is_active: bool) {
        self.widget_state.flags.set(PodFlags::ACTIVE, is_active);
    }

    pub fn set_handled(&mut self, is_handled: bool) {
        self.is_handled = is_handled;
    }

    pub fn is_handled(&self) -> bool {
        self.is_handled
    }
}

/// A macro for implementing methods on multiple contexts.
///
/// There are a lot of methods defined on multiple contexts; this lets us only
/// have to write them out once.
macro_rules! impl_context_method {
    ($ty:ty,  { $($method:item)+ } ) => {
        impl $ty { $($method)+ }
    };
    ( $ty:ty, $($more:ty),+, { $($method:item)+ } ) => {
        impl_context_method!($ty, { $($method)+ });
        impl_context_method!($($more),+, { $($method)+ });
    };
}

impl_context_method!(
    EventCtx<'_>,
    PaintCtx<'_>,
    LayoutCtx<'_>,
    LifeCycleCtx<'_>,
    {
        pub fn is_hot(&self) -> bool {
            self.widget_state.flags.contains(PodFlags::HOT)
        }

        pub fn is_active(&self) -> bool {
            self.widget_state.flags.contains(PodFlags::ACTIVE)
        }

        pub fn add_msg<T: Any>(&mut self, id_path: &IdPath, msg: T) {
            let message = Message {
                id_path: id_path.clone(),
                body: Box::new(msg),
            };
            self.cx_state.messages.push_back(message);
        }
    }
);

impl_context_method!(EventCtx<'_>, LifeCycleCtx<'_>, {
    pub fn request_layout(&mut self) {
        self.widget_state.flags |= PodFlags::REQUEST_LAYOUT;
    }
});

// Methods on EventCx, UpdateCx, LifeCycleCx and LayoutCx
impl_context_method!(EventCtx<'_>, LifeCycleCtx<'_>, LayoutCtx<'_>, {
    pub fn request_paint(&mut self) {
        self.widget_state.flags |= PodFlags::REQUEST_PAINT;
    }

    pub fn request_view_context(&mut self) {
        self.widget_state.flags |= PodFlags::VIEW_CONTEXT_CHANGED;
    }
});
